7. Authorisatie en request headers

Om beveiligde endpoints te kunnen gebruiken, moet je eerst inloggen als gebruiker en een JWT-token ophalen. Deze token stuur je mee met ieder opvolgend request: dit bewijst je identiteit bij elke API-aanroep.

Jezelf authoriseren (inloggen)

De API biedt een standaard, onbeveiligd endpoint voor inloggen:

POST /api/login

Om in te loggen stuur je een POST-request met de inloggegevens van een bestaande gebruiker. Hierbij is het meesturen van het e-mailadres en wachtwoord verplicht:

{
  "email": "[email protected]",
  "password": "admin123"
}

Bij een succesvolle inlogpoging ontvang je een response met de user-informatie en een JWT-token:

{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "user": {
    "email": "[email protected]",
    "roles": [
      "admin"
    ]
  }
}

Dit token bevat gecodeerde informatie over de gebruiker, waaronder de rollen die aan de gebruiker zijn toegewezen. Dit kun je zien als het identiteitsbewijs van jouw gebruiker. Bij ieder verzoek dat je hierna zult maken, zul je telkens de token moeten meesturen om jezelf als het ware te 'legitimeren'.

Hoelang blijft een token geldig?

JWT-tokens hebben een vervaldatum- en tijd. Nadat een token is verlopen, zul je opnieuw moeten inloggen om een nieuwe token op te halen. De tokens die worden uitgegeven door deze API, zijn één uur geldig. Het is daarom belangrijk om in jouw code altijd eerst te checken of een token nog geldig is, voor je deze gebruikt om requests te maken. Dit voorkomt onnodige requests.

Wanneer je een gebruiker uit wil loggen, hoef je geen verzoek te doen naar de API. Je verwijdert simpelweg de token uit de localStorage.

Voorbeeld: Inlogrequest met axios

Hier is een voorbeeld van hoe je kunt inloggen met JavaScript en de JWT-token kunt ophalen:

@todo hier moet nog een base-url bij!

async function login() {
	try {
		// stuur een POST-request naar het juiste endpoint
		const response = await axios.post('/api/login', {
			email: '[email protected]',
			password: 'SuperAdmin3000!'
		});
		// log de response in de console
		console.log(response.data);

		// Sla de token op voor later gebruik (dit gebeurt normaliter in de context)
		localStorage.setItem('token', response.data.token);
	} catch (e) {
		console.error('Inlogfout:', e);
	}
}

// P.S. de functie moet nog wel aangeroepen worden!

Request headers

Om beveiligde endpoints aan te spreken nadat je een JWT-token hebt verkregen, heb je doorgaans twee belangrijke request headers nodig: de Authorization header en de novi-education-project-id header.

De Authorization header maakt gebruik van het Bearer-schema, gevolgd door een spatie en de token. Deze header ziet er zo uit:

Authorization: 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...'

Daarnaast bevat de novi-education-project-id header de unieke identifier (GUID) van je project:

'novi-education-project-id': 'ed30b6ec-f22a-4fa0-aceb-7aa8504e3e98'

Deze header is bedoeld als legitimatiebewijs voor jouw project. Op basis van deze GUID weet de API met welke projectomgeving en bijbehorende database er gewerkt moet worden. Zo zorg je ervoor dat je alleen toegang krijgt tot je eigen data, en niet tot die van anderen. Je project specifieke GUID wordt aan je verstrekt bij het aanmaken van jouw project, of je kunt deze terugvinden in je projectinstellingen.

Voorbeeld: beveiligde gegevens ophalen

@todo de base-url moet nodig worden toegevoegd

Hier is een volledig voorbeeld van het ophalen van gegevens uit een beveiligde collectie:

async function fetchProducts() {
	// Haal het opgeslagen token op uit de localStorage
	const token = localStorage.getItem('token');

	// Controleer of er een token is EN of deze nog geldig is (maak hier een eigen functie voor!)
	// zo niet, dan beeïndigen we de functie
	if (!token || !isTokenValid(token)) {
		console.error('Niet ingelogd of token verlopen');
		return;
	}

	try {
		const response = await axios.get('/api/products', {
			headers: {
				'Authorization': `Bearer ${token}`,
				'novi-education-project-id': 'ed30b6ec-f22a-4fa0-aceb-7aa8504e3e98'
			}
		});

		console.log(response.data);
	} catch (e) {
		console.error('Fout bij ophalen producten:', e);
	}
}

Voorbeeld: Items toevoegen

Hier is een voorbeeld van het toevoegen van een nieuw item aan een products-collectie. In tegenstelling tot hoe je dit hebt aangepakt bij het vullen van de database, geef je via requests geen id mee. De database genereert deze zelf, om te kunnen garanderen dat er geen dubbele sleutels voorkomen.

import axios from 'axios';

async function addProduct() {
	const token = localStorage.getItem('token');

	try {
		const response = await axios.post('/api/products', {
			name: 'Nieuw Product',
			description: 'Een super cool nieuw product',
			price: 19.99,
			categoryId: 1,
		}, {
			headers: {
				'Authorization': `Bearer ${token}`,
				'novi-education-project-id': 'ed30b6ec-f22a-4fa0-aceb-7aa8504e3e98',
			}
		});

		console.log(response.data);
	} catch (e) {
		console.error('Fout bij toevoegen product:', e);
	}
}

Voorbeeld: API helpers in React

@todo dit misschien weg of naar de frontend-content?

In React is het handig om een API-helper te maken die deze logica centraliseert:

// api.js
import axios from 'axios';

const API_URL = '/api';
// Let op: api-keys zoals deze horen thuis in een .env bestand!
const PROJECT_ID = 'ed30b6ec-f22a-4fa0-aceb-7aa8504e3e98';

// Maak een Axios-instantie met basisconfiguratie
const apiClient = axios.create({
	baseURL: API_URL,
	headers: {
		'Content-Type': 'application/json',
		'novi-education-project-id': PROJECT_ID,
	}
});

// Voeg interceptor toe om automatisch de Authorization header mee te sturen
apiClient.interceptors.request.use(config => {
	const token = localStorage.getItem('authToken');
	if (token) {
		config.headers['Authorization'] = `Bearer ${token}`;
	}
	return config;
});

// Voeg interceptor toe om 401 fouten op te vangen
apiClient.interceptors.response.use(
	response => response,
	error => {
		if (error.response && error.response.status === 401) {
			localStorage.removeItem('authToken');
			navgate('/login');
		}
		return Promise.reject(error);
	}
);

// Exporteer helper-methoden
export const api = {
	get(endpoint) {
		return apiClient.get(endpoint).then(res => res.data);
	},
	post(endpoint, data) {
		return apiClient.post(endpoint, data).then(res => res.data);
	},
	put(endpoint, data) {
		return apiClient.put(endpoint, data).then(res => res.data);
	},
	delete(endpoint) {
		return apiClient.delete(endpoint).then(res => res.data);
	}
};

Gebruik in een React component:

import {api} from './api';

function ProductList() {
    async function getProducts() {
        try {
            const data = await api.get('products');
            console.log(data);
        } catch (e) {
            console.error(e);
        }
    }
}

In de volgende sectie zullen we zien hoe je de Swagger UI kunt gebruiken om je API te verkennen en te testen.